package de.fhin.mas.agents.searcher;

import jade.core.AID;
import jade.lang.acl.ACLMessage;
import de.fhin.mas.MASEvents;
import de.fhin.mas.MASServices;
import de.fhin.mas.agents.MASBaseAgent;
import de.fhin.mas.agents.searcher.behaviours.AbortAndGotoRequestResponder;
import de.fhin.mas.agents.searcher.behaviours.ChangeOfLocationNecessaryRequestInitiator;
import de.fhin.mas.agents.searcher.behaviours.EnvironmentScan;
import de.fhin.mas.agents.searcher.behaviours.IncomingInformHandler;
import de.fhin.mas.agents.searcher.behaviours.MovementOfPlatformNecessaryContractNetResponder;
import de.fhin.mas.agents.searcher.behaviours.NewOrderContractNetResponder;
import de.fhin.mas.agents.searcher.behaviours.ResumeContractNetResponder;
import de.fhin.mas.agents.searcher.behaviours.SystemStopRequiredRequestResponder;
import de.fhin.mas.agents.searcher.behaviours.TargetSearch;
import de.fhin.mas.agents.searcher.behaviours.UpdateServices;
import de.fhin.mas.ontology.SearchableObject;
import de.fhin.mas.util.ArgumentCheck;

/**
 * Der <tt>SearcherAgent</tt> kann die Aufgaben Zielsuche und Umgebungsscan im
 * MAS übernehmen.
 * 
 * @author Alexander Weickmann
 * @author Thomas Schläfer
 * @author Andreas Schmidl
 */
public class SearcherAgent extends MASBaseAgent {

	/** Die eindeutige ID zur Serialisierung über das Netzwerk. */
	private static final long serialVersionUID = -1995572011793480285L;

	/** Dieser Task führt die Zielsuche durch. */
	private final TargetSearch targetSearch;

	/** Dieser Task führt den Umgebungsscan durch. */
	private final EnvironmentScan environmentScan;

	/**
	 * Dieser Task initiiert <tt>change_of_location_necessary</tt> -
	 * <tt>REQUEST</tt>s wenn nötig.
	 */
	private final ChangeOfLocationNecessaryRequestInitiator changeOfLocNecInitiator;

	/** Dieses Flag gibt an, ob die Zielsuche gerade aktiv ist. */
	private boolean targetSearchActive;

	/** Dieses Flag gibt an, ob die Zielsuche gerade pausiert ist. */
	private boolean targetSearchPaused;

	/** Dieses Flag gibt an, ob der Umgebungsscan gerade aktiv ist. */
	private boolean environmentScanActive;

	/** AID des zur Verfügung stehenden Managers */
	private AID manager;

	/** AID des zur Verfügung stehenden Navigators */
	private AID navigator;

	/** AID des zur Verfügung stehenden Greifers */
	private AID gripper;

	/**
	 * Erzeugt einen neuen <tt>SearcherAgent</tt>.
	 */
	public SearcherAgent() {
		super(MASServices.SEARCHER);

		this.targetSearch = new TargetSearch(this);
		this.environmentScan = new EnvironmentScan(this);

		this.changeOfLocNecInitiator = new ChangeOfLocationNecessaryRequestInitiator(
				this);

		this.targetSearchActive = false;
		this.targetSearchPaused = false;
		this.environmentScanActive = false;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	protected void addBehaviours() {
		addBehaviour(new UpdateServices(this));
		addBehaviour(new IncomingInformHandler());

		addBehaviour(new NewOrderContractNetResponder(this));
		addBehaviour(new ResumeContractNetResponder(this));
		addBehaviour(new MovementOfPlatformNecessaryContractNetResponder(this));

		addBehaviour(new AbortAndGotoRequestResponder(this));
		addBehaviour(new SystemStopRequiredRequestResponder(this));
		addBehaviour(changeOfLocNecInitiator);
	}

	/**
	 * Startet die Zielsuche nach einem gegebenen Zielobjekt.
	 * <p>
	 * Falls die Zielsuche bereits aktiv ist sucht diese nun nach dem neuen
	 * Zielobjekt.
	 * 
	 * @param target
	 *            Das zu suchende Zielobjekt.
	 * 
	 * @throws NullPointerException
	 *             Falls <tt>target</tt> <tt>null</tt> ist.
	 */
	public void startTargetSearch(SearchableObject target) {
		ArgumentCheck.notNull(target);

		targetSearch.setTargetObject(target);

		// "Simulation" zurücksetzen.
		targetSearch.resetSimulation();

		if (!(isTargetSearchActive())) {
			addBehaviour(targetSearch);
		}
		targetSearchActive = true;
		targetSearchPaused = false;

		ACLMessage inform = new ACLMessage(ACLMessage.INFORM);
		inform.setConversationId(MASEvents.TARGET_SEARCH_STARTED.getName());
		sendToAll(inform);
	}

	/**
	 * Pausiert die Zielsuche. Beim Pausieren kann die Suche nach dem Zielobjekt
	 * zu einem späteren Zeitpunkt wieder aufgenommen werden.
	 * <p>
	 * Diese Methode kann auch aufgerufen werden, wenn die Zielsuche momentan
	 * gar nicht aktiv ist; in diesem Fall tut sie gar nichts.
	 */
	public void pauseTargetSearch() {
		if (!(isTargetSearchActive())) {
			return;
		}

		removeBehaviour(targetSearch);
		targetSearchActive = false;
		targetSearchPaused = true;

		ACLMessage inform = new ACLMessage(ACLMessage.INFORM);
		inform.setConversationId(MASEvents.TARGET_SEARCH_PAUSED.getName());
		sendToAll(inform);
	}

	/**
	 * Setzt die Zielsuche fort.
	 * <p>
	 * Diese Methode hat keine Auswirkung, falls die Zielsuche zum Zeitpunkt des
	 * Aufrufes nicht pausiert ist.
	 */
	public void resumeTargetSearch() {
		if (!(isTargetSearchPaused())) {
			return;
		}

		addBehaviour(targetSearch);
		targetSearchActive = true;
		targetSearchPaused = false;

		ACLMessage inform = new ACLMessage(ACLMessage.INFORM);
		inform.setConversationId(MASEvents.TARGET_SEARCH_RESUMED.getName());
		sendToAll(inform);
	}

	/**
	 * Bricht die Zielsuche ab. Beim Abbrechen kann die Suche nach dem
	 * Zielobjekt zu einem späteren Zeitpunkt nicht wieder aufgenommen werden.
	 * <p>
	 * Falls die Zielsuche gerade nicht aktiv oder pausiert ist passiert gar
	 * nichts.
	 */
	public void cancelTargetSearch() {
		if (!(isTargetSearchActive()) && !(isTargetSearchPaused())) {
			return;
		}

		targetSearch.setTargetObject(null);
		removeBehaviour(targetSearch);
		targetSearchActive = false;
		targetSearchPaused = false;

		ACLMessage inform = new ACLMessage(ACLMessage.INFORM);
		inform.setConversationId(MASEvents.TARGET_SEARCH_CANCELED.getName());
		sendToAll(inform);
	}

	/**
	 * Aktiviert die Umgebungssuche.
	 * <p>
	 * Falls diese bereits aktiv ist, so passiert gar nichts.
	 */
	public void startEnvironmentScan() {
		if (isEnvironmentScanActive()) {
			return;
		}

		addBehaviour(environmentScan);
		environmentScanActive = true;

		ACLMessage inform = new ACLMessage(ACLMessage.INFORM);
		inform.setConversationId(MASEvents.ENVIRONMENT_SCAN_STARTED.getName());
		sendToAll(inform);
	}

	/**
	 * Deaktiviert die Umgebungssuche.
	 * <p>
	 * Falls diese nicht aktiv ist, so passiert gar nichts.
	 */
	public void cancelEnvironmentScan() {
		if (!(isEnvironmentScanActive())) {
			return;
		}

		removeBehaviour(environmentScan);
		environmentScanActive = false;

		ACLMessage inform = new ACLMessage(ACLMessage.INFORM);
		inform.setConversationId(MASEvents.ENVIRONMENT_SCAN_CANCELED.getName());
		sendToAll(inform);
	}

	/** Gibt zurück, ob der Umgebungsscan gerade aktiv ist. */
	public boolean isEnvironmentScanActive() {
		return environmentScanActive;
	}

	/** Gibt zurück, ob die Zielsuche gerade aktiv ist. */
	public boolean isTargetSearchActive() {
		return targetSearchActive;
	}

	/** Gibt zurück, ob die Zielsuche gerade pausiert ist. */
	public boolean isTargetSearchPaused() {
		return targetSearchPaused;
	}

	/**
	 * Setzt den referenzierten Manager.
	 * 
	 * @param manager
	 *            Die <tt>AID</tt> des Managers, darf auch <tt>null</tt> sein.
	 */
	public void setManager(AID manager) {
		this.manager = manager;
	}

	/**
	 * Gibt die <tt>AID</tt> des Managers zurück, falls dieser Service verfügbar
	 * ist. Wenn kein Manager-Service auf der Plattform angeboten wird, wird
	 * <tt>null</tt> zurückgegeben.
	 */
	public AID getManager() {
		return manager;
	}

	/**
	 * Setzt den referenzierten Navigator.
	 * 
	 * @param navigator
	 *            Die <tt>AID</tt> des Navigators, darf auch <tt>null</tt> sein.
	 */
	public void setNavigator(AID navigator) {
		this.navigator = navigator;
	}

	/**
	 * Gibt die <tt>AID</tt> des Navigators zurück, falls dieser Service
	 * verfügbar ist. Wenn kein Navigator-Service auf der Plattform angeboten
	 * wird, wird <tt>null</tt> zurückgegeben.
	 */
	public AID getNavigator() {
		return navigator;
	}

	/**
	 * Setzt den referenzierten Greifer.
	 * 
	 * @param gripper
	 *            Die <tt>AID</tt> des Greifers, darf auch <tt>null</tt> sein.
	 */
	public void setGripper(AID gripper) {
		this.gripper = gripper;
	}

	/**
	 * Gibt die <tt>AID</tt> des Greifers zurück, falls dieser Service verfügbar
	 * ist. Wenn kein Greifer-Service auf der Plattform angeboten wird, wird
	 * <tt>null</tt> zurückgegeben.
	 */
	public AID getGripper() {
		return gripper;
	}

	/**
	 * Initiiert einen <tt>change_of_location_necessary</tt> - <tt>REQUEST</tt>.
	 */
	public void initChangeOfLocationNecessary() {
		// Request-Auslösung im Behaviour triggern
		changeOfLocNecInitiator.initRequest();

		// Behaviour entblocken
		changeOfLocNecInitiator.restart();
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	protected void addTopicSubsciptions() {
		// TODO sucher: topics (noch nicht benötigt)
	}

}
